#include <sobjc-commons/defines.h>
#include "command.h"
#include "context.h"
#include "parse.h"
#include "scope.h"
#include "type.h"

#define OBJC_TYPE_ID_NAME           "id"
#define OBJC_TYPE_CLASS_NAME        "Class"
#define OBJC_TYPE_SEL_NAME          "SEL"

static void finish_encode(char *);
static void encode_decl(char **, objc_scope_t, objc_node_t, const char *);
static void encode_expr(char **, objc_scope_t, objc_node_t);
static char *apply_func(const char *);
static char *refer_array(const char *);
static char *refer_struct(objc_scope_t, const char *, const char *);
static void scan_mem_ctype(const char **, char *);
static const char *get_size_t_ctype(objc_scope_t);
static char *refer_ptr(const char *);
static char *refer_address(const char *);
static void encode_msg_expr(char **, objc_scope_t, objc_node_t,
                            const char *, BOOL);

enum {
  NO_FLAG,
  HAS_PTR,
  MAY_NAMED_ID,
  UNSIGNED_QUAL,
  MAY_LONGLONG,
  MAY_UNSIGNED_LONGLONG
};

static const char *id_ctype;
static const char *cls_ctype;
static const char *sel_ctype;

static void
finish_encode(char *ctype)
{
  char *buf;
  int flag = NO_FLAG;

  buf = ctype;
  while (*ctype) {
    switch (flag) {
    case NO_FLAG:
      switch (*ctype) {
      case OBJC_CTYPE_PTR_CODE:
        flag = HAS_PTR;
        ctype++;
        continue;
      case OBJC_CTYPE_UNSIGNED_CODE:
        flag = UNSIGNED_QUAL;
        ctype++;
        continue;
      case OBJC_CTYPE_LONG_CODE:
        flag = MAY_LONGLONG;
        ctype++;
        continue;
      }
      break;
    case HAS_PTR:
      switch (*ctype) {
      case OBJC_CTYPE_ID_CODE:
        flag = MAY_NAMED_ID;
        ctype++;
        continue;
      case OBJC_CTYPE_VOID_CODE:
        *buf++ = OBJC_CTYPE_GENERIC_PTR_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      case OBJC_CTYPE_FUNC_BEGIN_CODE:
        flag = NO_FLAG;
        break;
      case OBJC_CTYPE_UNSIGNED_CODE:
        *buf++ = OBJC_CTYPE_PTR_CODE;
        flag = UNSIGNED_QUAL;
        ctype++;
        continue;
      case OBJC_CTYPE_LONG_CODE:
        *buf++ = OBJC_CTYPE_PTR_CODE;
        flag = MAY_LONGLONG;
        ctype++;
        continue;
      default:
        *buf++ = OBJC_CTYPE_PTR_CODE;
        flag = NO_FLAG;
        break;
      }
      break;
    case MAY_NAMED_ID:
      switch (*ctype) {
      case '{':
        *buf++ = OBJC_CTYPE_ID_CODE;
        flag = NO_FLAG;
        break;
      default:
        *buf++ = OBJC_CTYPE_PTR_CODE;
        *buf++ = OBJC_CTYPE_ID_CODE;
        flag = NO_FLAG;
      }
      break;
    case UNSIGNED_QUAL:
      switch (*ctype) {
      case OBJC_CTYPE_CHAR_CODE:
        *buf++ = OBJC_CTYPE_UCHAR_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      case OBJC_CTYPE_INT_CODE:
        *buf++ = OBJC_CTYPE_UINT_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      case OBJC_CTYPE_SHORT_CODE:
        *buf++ = OBJC_CTYPE_USHORT_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      case OBJC_CTYPE_LONG_CODE:
        flag = MAY_UNSIGNED_LONGLONG;
        ctype++;
        continue;
      default:
        *buf++ = OBJC_CTYPE_UNSIGNED_CODE;
        flag = NO_FLAG;
        break;
      }
      break;
    case MAY_LONGLONG:
      switch (*ctype) {
      case OBJC_CTYPE_LONG_CODE:
        *buf++ = OBJC_CTYPE_LONGLONG_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      default:
        *buf++ = OBJC_CTYPE_LONG_CODE;
        flag = NO_FLAG;
        break;
      }
      break;
    case MAY_UNSIGNED_LONGLONG:
      switch (*ctype) {
      case OBJC_CTYPE_LONG_CODE:
        *buf++ = OBJC_CTYPE_ULONGLONG_CODE;
        flag = NO_FLAG;
        ctype++;
        continue;
      default:
        *buf++ = OBJC_CTYPE_ULONG_CODE;
        flag = NO_FLAG;
        break;
      }
      break;
    default:
      flag = NO_FLAG;
      break;
    }
    *buf++ = *ctype++;
  }
  *buf = 0;
}

char *
objc_ctype_from_decl(objc_scope_t sc,
                     objc_node_t spcr,
                     objc_node_t dclr)
{
  char *name = NULL, *base, *buf, *ctype;

  base = buf = ALLOC_N(char, 1024);
  *buf = 0;
  if (dclr) {
    name = objc_node_dclr_name(dclr);
    encode_decl(&buf, sc, dclr, name);
  }
  encode_decl(&buf, sc, spcr, name);

  *buf = 0;
  ctype = ALLOC_N(char, (ptrdiff_t)(buf - base) + 1);
  strcpy(ctype, base);
  finish_encode(ctype);
  return ctype;
}

static void
encode_decl(char **buf,
            objc_scope_t sc,
            objc_node_t node,
            const char *var)
{
  if (node == NULL)
    return;

  switch (node->type) {
  case OBJC_DECL_SPCR_LIST_NODE:
  case OBJC_SPCR_QUAL_LIST_NODE:
  case OBJC_STRUCT_DCLR_LIST_NODE:
  case OBJC_STRUCT_DECL_LIST_NODE:
    {
      int i;

      for (i = 0; i < node->a.l.nsubs; i++)
        encode_decl(buf, sc, node->a.l.subs[i], var);
    }
    return;

  case OBJC_ABS_DCLR_NODE:
    encode_decl(buf, sc, node->a.abs_dclr.ptr, var);
    encode_decl(buf, sc, node->a.abs_dclr.dir_abs_dclr, var);
    return;

  case OBJC_ARRAY_DIR_DCLR_NODE:
    encode_decl(buf, sc, node->a.array_dir_dclr.dir_dclr, var);
    break;

  case OBJC_BASE_DCLR_NODE:
    if (node->a.base_dclr.dir_dclr->type == OBJC_FUNC_DIR_DCLR_NODE) {
      encode_decl(buf, sc, node->a.base_dclr.dir_dclr, var);
      encode_decl(buf, sc, node->a.base_dclr.ptr, var);
    } else {
      encode_decl(buf, sc, node->a.base_dclr.ptr, var);
      encode_decl(buf, sc, node->a.base_dclr.dir_dclr, var);
    }
    return;

  case OBJC_CLS_NAME_NODE:
    if (node->a.cls_name.prtcs)
      *buf += sprintf(*buf, "%c{%s<%s>}", OBJC_CTYPE_ID_CODE,
                      node->a.cls_name.name,
                      objc_str_list_join(node->a.cls_name.prtcs, ","));
    else
      *buf += sprintf(*buf, "%c{%s}", OBJC_CTYPE_ID_CODE, node->a.cls_name.name);
    return;

  case OBJC_CONST_NODE:
    switch (node->a.const_val.tag) {
    case INT_CONST:
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
      return;
    case ENUM_CONST:
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
      return;
    case CHAR_CONST:
      *(*buf)++ = OBJC_CTYPE_CHAR_CODE;
      return;
    case FLOAT_CONST:
      *(*buf)++ = OBJC_CTYPE_FLOAT_CODE;
      return;
    }
    return;
  
  case OBJC_CONST_DIR_ABS_DCLR_NODE:
    *(*buf)++ = OBJC_CTYPE_ARRAY_CODE;
    encode_decl(buf, sc, node->a.const_dir_abs_dclr.dir_abs_dclr, var);
    return;

  case OBJC_DCLR_NODE:
    encode_decl(buf, sc, node->a.dclr.base_dclr, var);
    return;

  case OBJC_ENUM_NODE:
    {
      objc_scope_entry_t entry;
      char *ctype;

      entry = objc_scope_get_var(sc, node->a.enmr.name);
      ctype = objc_scope_entry_get_ctype(entry);
      *buf += sprintf(*buf, "%c%s%c", OBJC_CTYPE_ENUM_BEGIN_CODE,
                      ctype, OBJC_CTYPE_ENUM_END_CODE);
    }
    return;

  case OBJC_ENUM_SPCR_NODE:
    *(*buf)++ = OBJC_CTYPE_ENUM_BEGIN_CODE;
    if (node->a.enum_spcr.name)
      *buf += sprintf(*buf, "%s", node->a.enum_spcr.name);
    *(*buf)++ = OBJC_CTYPE_ENUM_END_CODE;
    return;

  case OBJC_FUNC_DIR_DCLR_NODE:
    encode_decl(buf, sc, node->a.func_dir_dclr.dir_dclr, var);
    *(*buf)++ = OBJC_CTYPE_FUNC_BEGIN_CODE;
    encode_decl(buf, sc, node->a.func_dir_dclr.param_list, var);
    *(*buf)++ = OBJC_CTYPE_FUNC_END_CODE;
    return;

  case OBJC_FUNC_DEF_NODE:
    if (node->a.func_def.decl_list) {
      *(*buf)++ = OBJC_CTYPE_FUNC_BEGIN_CODE;
      encode_decl(buf, sc, node->a.func_def.decl_list, NULL);
      *(*buf)++ = OBJC_CTYPE_FUNC_END_CODE;
    } else
      encode_decl(buf, sc, node->a.func_def.dclr,
                  objc_node_dclr_name(node->a.func_def.dclr));
    encode_decl(buf, sc, node->a.func_def.decl_spcr_list, NULL);
    return;

  case OBJC_IDENT_NODE:
    {
      objc_ivar_t ivar;
      objc_scope_entry_t entry;

      if (var && STREQ(node->a.sval, var))
        return;
      else {
        /*
        entry = objc_scope_get_ctype_for_var(objc_current_scope, node->a.sval);
        if (!entry)
          objc_print_error("not found entry for '%s'", node->a.sval);
        *buf += sprintf(*buf, "%s", entry->a.type.ctype);
        */
      }
    }
    return;

  case OBJC_INIT_DCLR_NODE:
    encode_decl(buf, sc, node->a.init_dclr.dclr, var);
    return;

  case OBJC_KW_DCLR_NODE:
    if (node->a.kw_dclr.mtype)
      encode_decl(buf, sc, node->a.kw_dclr.mtype, var);
    else
      *(*buf)++ = OBJC_CTYPE_ID_CODE;
    return;

  case OBJC_MTH_DCLR_NODE:
    {
      objc_class_t cls;

      *(*buf)++ = OBJC_CTYPE_FUNC_BEGIN_CODE;
      if (node->a.mth_dclr.mth_spcr->a.mth_spcr.rtype)
        *(*buf)++ = OBJC_CTYPE_ID_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_CLASS_CODE;

      cls = objc_scope_get_class(sc);
      if (cls)
        *buf += sprintf(*buf, "{%s}", cls->name);

      *buf += sprintf(*buf, ";%c;", OBJC_CTYPE_SEL_CODE);
      encode_decl(buf, sc, node->a.mth_dclr.mth_sel, NULL);
      *(*buf)++ = OBJC_CTYPE_FUNC_END_CODE;

      if (node->a.mth_dclr.mth_spcr->a.mth_spcr.mtype)
        encode_decl(buf, sc, node->a.mth_dclr.mth_spcr->
                    a.mth_spcr.mtype, NULL);
      else
        *(*buf)++ = OBJC_CTYPE_ID_CODE;
    }
    return;

  case OBJC_MTH_UNARY_SEL_NODE:
    /* no arguments */
    return;

  case OBJC_MTH_KW_SEL_NODE:
    {
      objc_node_t sel;
      int i;

      sel = node->a.mth_kw_sel.sel;
      for (i = 0; i < sel->a.l.nsubs; i++) {
        encode_decl(buf, sc, sel->a.l.subs[i], NULL);
        *(*buf)++ = OBJC_CTYPE_TERM_CODE;
      }

      if (node->a.mth_kw_sel.vargs)
        *(*buf)++ = OBJC_CTYPE_ELLIPSIS_CODE;
    }
    return;

  case OBJC_PARAM_DECL_NODE:
    encode_decl(buf, sc, node->a.param_decl.dclr,
                objc_node_dclr_name(node->a.param_decl.dclr));
    encode_decl(buf, sc, node->a.param_decl.decl_spcr_list, NULL);
    return;

  case OBJC_PARAM_DIR_ABS_DCLR_NODE:
    encode_decl(buf, sc, node->a.param_dir_abs_dclr.dir_abs_dclr, var);
    *(*buf)++ = OBJC_CTYPE_FUNC_BEGIN_CODE;
    encode_decl(buf, sc, node->a.param_dir_abs_dclr.param_type_list, var);
    *(*buf)++ = OBJC_CTYPE_FUNC_END_CODE;
    return;

  case OBJC_PARAM_TYPE_LIST_NODE:
    {
      int i;

      for (i = 0; i < node->a.param_type_list.param_list->a.l.nsubs; i++) {
        encode_decl(buf, sc, node->a.param_type_list.param_list->
                    a.l.subs[i], var);
        *(*buf)++ = OBJC_CTYPE_TERM_CODE;
      }
      if (node->a.param_type_list.vargs)
        *(*buf)++ = OBJC_CTYPE_ELLIPSIS_CODE;
    }
    return;

  case OBJC_PTR_NODE:
    *(*buf)++ = OBJC_CTYPE_PTR_CODE;
    encode_decl(buf, sc, node->a.ptr.ptr, var);
    return;

  case OBJC_STORAGE_CLS_SPCR_NODE:
    /* ignore */
    return;

  case OBJC_STRUCT_DECL_NODE:
    {
      objc_node_t dclrs, dclr;
      int i;
      char *name;

      dclrs = node->a.struct_decl.dclr_list;
      for (i = 0; i < dclrs->a.l.nsubs; i++) {
        dclr = dclrs->a.l.subs[i];
        name = objc_node_dclr_name(dclr);
        *buf += sprintf(*buf, "%s%c", name, OBJC_CTYPE_MEM_NAME_SEP_CODE);
        encode_decl(buf, sc, dclr, name);
        encode_decl(buf, sc, node->a.struct_decl.spcr_qual_list, NULL);
        if (i > 0)
          *(*buf)++ = OBJC_CTYPE_TERM_CODE;
      }
    }
    return;

  case OBJC_STRUCT_DCLR_NODE:
    if (node->a.struct_dclr.expr)
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
    else
      encode_decl(buf, sc, node->a.struct_dclr.dclr, NULL);
    return;

  case OBJC_SU_SPCR_NODE:
    {
      objc_scope_entry_t entry;
      objc_node_t decls = NULL, parent = NULL;
      int i;
      BOOL ref_self = NO;

      if (node->a.su_spcr.type)
        *(*buf)++ = OBJC_CTYPE_STRUCT_BEGIN_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_UNION_BEGIN_CODE;

      if (node->a.su_spcr.name)
        *buf += sprintf(*buf, "%s", node->a.su_spcr.name);

      if (node->a.su_spcr.decl_list)
        decls = node->a.su_spcr.decl_list;
      else {
        parent = node->parent;
        while (parent) {
          if (parent->type == OBJC_SU_SPCR_NODE &&
              STREQ(parent->a.su_spcr.name, node->a.su_spcr.name)) {
            ref_self = YES;
          }
          parent = parent->parent;
        }

        if (!ref_self) {
          entry = objc_scope_get_struct(sc, node->a.su_spcr.name);
          if (entry) {
              decls = entry->a.type.spcr->a.su_spcr.decl_list;
          } else
            objc_print_error("unknown struct or union -- %s\n",
                             node->a.su_spcr.name);
        }
      }

      if (decls) {
        if (node->a.su_spcr.name)
          *(*buf)++ = OBJC_CTYPE_SU_NAME_SEP_CODE;
        for (i = 0; i < decls->a.l.nsubs; i++) {
          encode_decl(buf, sc, decls->a.l.subs[i], NULL);
          *(*buf)++ = OBJC_CTYPE_TERM_CODE;
        }
      }

      if (node->a.su_spcr.type)
        *(*buf)++ = OBJC_CTYPE_STRUCT_END_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_UNION_END_CODE;
    }
    return;

  case OBJC_TYPEDEF_NAME_NODE:
    {
      objc_scope_entry_t entry;
      const char *ctype;

      if (STREQ(node->a.sval, OBJC_TYPE_ID_NAME))
        *(*buf)++ = OBJC_CTYPE_ID_CODE;
      else if (STREQ(node->a.sval, OBJC_TYPE_SEL_NAME))
        *(*buf)++ = OBJC_CTYPE_SEL_CODE;
      else if (STREQ(node->a.sval, OBJC_TYPE_CLASS_NAME))
        *(*buf)++ = OBJC_CTYPE_CLASS_CODE;
      else {
        entry = objc_scope_get_type(sc, node->a.sval);
        ctype = objc_scope_entry_get_ctype(entry);
        if (ctype)
          *buf += sprintf(*buf, "%s", ctype);
      }
    }
    return;

  case OBJC_TYPE_NAME_NODE:
    if (node->a.type_name.abs_dclr) {
      encode_decl(buf, sc, node->a.type_name.abs_dclr,
                  objc_node_dclr_name(node->a.type_name.abs_dclr));
    }
    encode_decl(buf, sc, node->a.type_name.spcr_qual_list, NULL);
    return;

  case OBJC_TYPE_QUAL_NODE:
    switch (node->a.word) {
    case CONST: case CONST2: case CONST3:
      *(*buf)++ = OBJC_CTYPE_CONST_CODE;
      return;
    }
    return;

  case OBJC_TYPE_SPCR_NODE:
    switch (node->a.word) {
    case SIGNED:    break;
    case UNSIGNED:  *(*buf)++ = OBJC_CTYPE_UNSIGNED_CODE; return;
    case CHAR:      *(*buf)++ = OBJC_CTYPE_CHAR_CODE; return;
    case SHORT:     *(*buf)++ = OBJC_CTYPE_SHORT_CODE; return;
    case INT:       *(*buf)++ = OBJC_CTYPE_INT_CODE; return;
    case LONG:      *(*buf)++ = OBJC_CTYPE_LONG_CODE; return;
    case FLOAT:     *(*buf)++ = OBJC_CTYPE_FLOAT_CODE; return;
    case DOUBLE:    *(*buf)++ = OBJC_CTYPE_DOUBLE_CODE; return;
    case VOID:      *(*buf)++ = OBJC_CTYPE_VOID_CODE; return;
    case INSTANCE:  *(*buf)++ = OBJC_CTYPE_INSTANCE_CODE; return;
    case SELF:      *(*buf)++ = OBJC_CTYPE_XSELF_CODE; return;
    default:
      objc_print_error("ctype: unknown type-specifier (%d)\n", node->a.word);
      break;
    }
    return;

  default:
    objc_print_error("ctype: cannot encode %s", objc_node_name(node));
  }
}

char *
objc_ctype_from_expr(objc_scope_t sc, objc_node_t node)
{
  char *base, *buf, *ctype;
  
  base = buf = ALLOC_N(char, 512);
  *buf = 0;
  encode_expr(&buf, sc, node);
  *buf = 0;
  ctype = ALLOC_N(char, (ptrdiff_t)(buf - base) + 1);
  strcpy(ctype, base);
  finish_encode(ctype);
  return ctype;
}

static void
encode_expr(char **buf, objc_scope_t sc, objc_node_t node)
{
  if (node == NULL)
    return;

  switch (node->type) {
  case OBJC_DECL_SPCR_LIST_NODE:
  case OBJC_EXPR_NODE:
  case OBJC_SPCR_QUAL_LIST_NODE:
  case OBJC_STRUCT_DCLR_LIST_NODE:
  case OBJC_STRUCT_DECL_LIST_NODE:
    {
      int i;
      for (i = 0; i < node->a.l.nsubs; i++)
        encode_expr(buf, sc, node->a.l.subs[i]);
    }
    return;

  case OBJC_ADD_EXPR_NODE:
  case OBJC_MULTI_EXPR_NODE:
  case OBJC_SHIFT_EXPR_NODE:
    /* FIXME: number type only */
    encode_expr(buf, sc, node->a.bin_expr.left);
    return;

  case OBJC_AND_EXPR_NODE:
  case OBJC_EQ_EXPR_NODE:
  case OBJC_EXCL_OR_EXPR_NODE:
  case OBJC_INCL_OR_EXPR_NODE:
  case OBJC_LOG_AND_EXPR_NODE:
  case OBJC_LOG_OR_EXPR_NODE:
  case OBJC_REL_EXPR_NODE:
    *(*buf)++ = OBJC_CTYPE_INT_CODE;
    return;

  case OBJC_ABS_DCLR_NODE:
    encode_expr(buf, sc, node->a.abs_dclr.dir_abs_dclr);
    encode_expr(buf, sc, node->a.abs_dclr.ptr);
    return;

  case OBJC_ARG_EXPR_LIST_NODE:
    /* encode last expression */
    encode_expr(buf, sc, node->a.l.subs[node->a.l.nsubs-1]);
    return;

  case OBJC_ARRAY_REF_EXPR_NODE:
    {
      char *ctype, *reftype;

      ctype = objc_ctype_from_expr(sc, node->a.array_ref_expr.array);
      reftype = refer_array(ctype);
      *buf += sprintf(*buf, "%s", reftype);
    }
    return;

  case OBJC_ASSIGN_EXPR_NODE:
    if (STREQ(node->a.bin_expr.op, "="))
      encode_expr(buf, sc, node->a.bin_expr.left);
    else
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
    return;

  case OBJC_CAST_EXPR_NODE:
    encode_expr(buf, sc, node->a.cast_expr.type);
    return;

  case OBJC_CLS_OBJ_RECV_NODE:
    *buf += sprintf(*buf, "%c{%s}", OBJC_CTYPE_CLASS_CODE, node->a.sval);
    return;

  case OBJC_COND_EXPR_NODE:
    encode_expr(buf, sc, node->a.cond_expr.t_expr);
    return;

  case OBJC_CONST_NODE:
    switch (node->a.const_val.tag) {
    case INT_CONST:
      if (STREQ(node->a.const_val.val, "0"))
        *(*buf)++ = OBJC_CTYPE_NIL_CODE;
      else
        *(*buf)++ = OBJC_CTYPE_INT_CODE;
      return;
    case CHAR_CONST:
      *(*buf)++ = OBJC_CTYPE_INT_CODE;
      return;
    case FLOAT_CONST:
      *(*buf)++ = OBJC_CTYPE_FLOAT_CODE;
      return;
    case ENUM_CONST:
      {
        objc_scope_entry_t entry;

        entry = objc_scope_get_var(sc, node->a.const_val.val);
        *(*buf)++ = OBJC_CTYPE_ENUM_BEGIN_CODE;
        *buf += sprintf(*buf, "%s", objc_scope_entry_get_ctype(entry));
        *(*buf)++ = OBJC_CTYPE_ENUM_END_CODE;
        return;
      }
    }
    return;

  case OBJC_ENC_EXPR_NODE:
    *(*buf)++ = OBJC_CTYPE_CONST_CODE;
    *(*buf)++ = OBJC_CTYPE_PTR_CODE;
    *(*buf)++ = OBJC_CTYPE_CONST_CODE;
    *(*buf)++ = OBJC_CTYPE_CHAR_CODE;
    return;

  case OBJC_FUNC_CALL_EXPR_NODE:
    {
      char *ctype, *rettype;

      ctype = objc_ctype_from_expr(sc, node->a.func_call_expr.func);
      rettype = apply_func(ctype);
      *buf += sprintf(*buf, "%s", rettype);
    }
    return;

  case OBJC_IDENT_NODE:
    {
      objc_class_t cls;
      objc_ivar_t ivar;
      objc_scope_entry_t entry;
      const char *ctype;

      cls = objc_scope_get_class(sc);
      if (STREQ(node->a.sval, "self") && cls)
        *(*buf)++ = OBJC_CTYPE_SELF_CODE;
      else if (STREQ(node->a.sval, "_cmd") && cls)
        *(*buf)++ = OBJC_CTYPE_SEL_CODE;
      else if (cls && (ivar = objc_class_get_ivar(cls, node->a.sval)) != NULL) {
        if (ivar->ctype == NULL)
          ivar->ctype =
            objc_ctype_from_decl(sc, ivar->node->a.ivar_decl.spcr_qual_list,
                                 ivar->node->a.ivar_decl.dclr);
        *buf += sprintf(*buf, "%s", ivar->ctype);
      } else {
        entry = objc_scope_get_var(sc, node->a.sval);
        if (!entry)
          objc_print_error("objc_ctype_from_expr(): not found entry for '%s'", node->a.sval);
        ctype = objc_scope_entry_get_ctype(entry);
        if (ctype)
          *buf += sprintf(*buf, "%s", ctype);
      }
    }
    return;

  case OBJC_INC_OR_DEC_EXPR_NODE:
    {
      char *base, *ctype;

      if (STREQ(node->a.inc_or_dec_expr.op, "*")) {
        base = objc_ctype_from_expr(sc, node->a.inc_or_dec_expr.expr);
        ctype = refer_ptr(base);
        *buf += sprintf(*buf, "%s", ctype);
      } else if (STREQ(node->a.inc_or_dec_expr.op, "&")) {
        base = objc_ctype_from_expr(sc, node->a.inc_or_dec_expr.expr);
        ctype = refer_address(base);
        *buf += sprintf(*buf, "%s", ctype);
      } else
        encode_expr(buf, sc, node->a.inc_or_dec_expr.expr);
    }
    return;

  case OBJC_KW_ARG_EXPR_NODE:
    encode_expr(buf, sc, node->a.kw_arg_expr.expr);
    return;

  case OBJC_KW_MSG_EXPR_NODE:
    encode_msg_expr(buf, sc, node->a.kw_msg_expr.recv,
                    node->a.kw_msg_expr.sel_name,
                    node->a.kw_msg_expr.self_is_inst);
    return;

  case OBJC_OBJC_STR_NODE:
    {
      objc_class_t cls;
      const char *name;

      name = objc_get_const_str_class_name();
      cls = objc_get_const_str_class();
      if (!cls)
        objc_print_error("class of constant string '%s' not found", name);
      else 
        *buf += sprintf(*buf, "%c{%s}", OBJC_CTYPE_ID_CODE, name);
    }
    return;

  case OBJC_PRTC_EXPR_NODE:
    *buf += sprintf(*buf, "%c{Protocol}", OBJC_CTYPE_ID_CODE);
    return;

  case OBJC_SEL_EXPR_NODE:
    {
      objc_scope_entry_t entry;

      if (!sel_ctype) {
        entry = objc_scope_get_type(sc, OBJC_TYPE_SEL_NAME);
        sel_ctype = objc_scope_entry_get_ctype(entry);
      }
      *buf += sprintf(*buf, "%s", sel_ctype);
    }
    return;

  case OBJC_SIZE_EXPR_NODE:
  case OBJC_SIZE_TYPE_EXPR_NODE:
    *buf += sprintf(*buf, "%s", get_size_t_ctype(sc));
    return;

  case OBJC_STORAGE_CLS_SPCR_NODE:
    /* ignore */
    return;

  case OBJC_STR_NODE:
    *(*buf)++ = OBJC_CTYPE_PTR_CODE;
    *(*buf)++ = OBJC_CTYPE_CONST_CODE;
    *(*buf)++ = OBJC_CTYPE_CHAR_CODE;
    return;

  case OBJC_SU_REF_EXPR_NODE:
    {
      char *ctype, *reftype;

      ctype = objc_ctype_from_expr(sc, node->a.su_ref_expr.expr);
      reftype = refer_struct(sc, ctype, node->a.su_ref_expr.name);
      *buf += sprintf(*buf, "%s", reftype);
    }
    return;

  case OBJC_SUPER_NODE:
    {
      objc_class_t cls;
      
      cls = objc_scope_get_class(sc);
      if (cls)
        *buf += sprintf(*buf, "%c{%s}", OBJC_CTYPE_SUPER_CODE, cls->name);
      else
        objc_print_error("cannot use 'super' in outer class implementation");
    }
    return;

  case OBJC_TYPEDEF_NAME_NODE:
    {
      objc_scope_entry_t entry;
      const char *ctype;

      entry = objc_scope_get_type(sc, node->a.sval);
      ctype = objc_scope_entry_get_ctype(entry);
      *buf += sprintf(*buf, "%s", ctype);
    }
    return;

  case OBJC_TYPE_NAME_NODE:
    /* use encode_decl() for pointer semantics */
    encode_decl(buf, sc, node->a.type_name.abs_dclr,
                objc_node_dclr_name(node->a.type_name.abs_dclr));
    encode_decl(buf, sc, node->a.type_name.spcr_qual_list, NULL);
    return;

  case OBJC_UNARY_MSG_EXPR_NODE:
    encode_msg_expr(buf, sc, node->a.unary_msg_expr.recv,
                    node->a.unary_msg_expr.sel_name,
                    node->a.unary_msg_expr.self_is_inst);
    return;

  default:
    objc_print_error("ctype: cannot encode expression `%s'",
                     objc_node_name(node));
  }
}

static char *
apply_func(const char *ctype)
{
  char *rettype;
  size_t count = 0, len;

  if (*ctype == OBJC_CTYPE_PTR_CODE)
    ctype++;

  while (*ctype) {
    if (*ctype == OBJC_CTYPE_FUNC_BEGIN_CODE)
      count++;
    else if (*ctype == OBJC_CTYPE_FUNC_END_CODE) {
      count--;
      if (!count) {
        ctype++;
        break;
      }
    }

    if (count)
      ctype++;
    else
      break;
  }

  len = strlen(ctype);
  rettype = ALLOC_N(char, len+1);
  strncpy(rettype, ctype, len);
  rettype[len] = 0;
  return rettype;
}

char *
objc_ctype_apply_func(const char *ctype)
{
  char *temp, *ret;

  temp = apply_func(ctype);
  ret = objc_strdup(temp);
  return ret;
}

char *
objc_ctype_get_arg_type(const char *ctype, int index)
{
  const char *orig;
  char *arg_type, *buf;
  size_t count = 0;
  int i = 0;

  orig = ctype;
  if (*ctype == OBJC_CTYPE_PTR_CODE)
    ctype++;

  if (*ctype == OBJC_CTYPE_FUNC_BEGIN_CODE) {
    buf = arg_type = ALLOC_N(char, strlen(ctype)+1);
    ctype++;

    while (*ctype) {
      switch (*ctype) {
      case OBJC_CTYPE_FUNC_BEGIN_CODE:
      case OBJC_CTYPE_STRUCT_BEGIN_CODE:
      case OBJC_CTYPE_UNION_BEGIN_CODE:
        count++;
        break;

      case OBJC_CTYPE_FUNC_END_CODE:
      case OBJC_CTYPE_STRUCT_END_CODE:
      case OBJC_CTYPE_UNION_END_CODE:
        if (count)
          count--;
        else
          objc_print_error("objc_ctype_get_arg_type(): parse error -- '%s'", orig);
        break;

      case OBJC_CTYPE_ELLIPSIS_CODE:
        if (!count) {
          sprintf(arg_type, "%c", OBJC_CTYPE_ELLIPSIS_CODE);
          return arg_type;
        }
        break;

      case OBJC_CTYPE_TERM_CODE:
        if (!count) {
          if (i == index)
            return arg_type;
          else {
            buf = arg_type;  
            *buf = 0;
            ctype++;
            i++;
            continue;
          }
        }
        break;
      }
      *buf = *ctype++;
      buf++;
      *buf = 0;
    }
    objc_print_error("objc_ctype_get_arg_type(): out of bounds %d", i);
  } else
    objc_print_error("objc_ctype_get_arg_type(): type is not function");
  return NULL;
}

static char *
refer_array(const char *ctype)
{
  char *reftype;
  BOOL rest = NO;
  int i = 0;

  reftype = ALLOC_N(char, strlen(ctype)+1);
  while (*ctype) {
    if (rest)
      reftype[i] = *ctype;
    else if (*ctype == OBJC_CTYPE_ARRAY_CODE)
      rest = YES;
    ctype++;
  }

  return reftype;
}

enum SU_REF_STATE {
  SU_NAME_STATE,
  MEM_NAME_STATE
};

static char *
refer_struct(objc_scope_t sc, const char *ctype, const char *name)
{
  objc_scope_entry_t entry;
  char *st_ctype, *un_ctype, *temp;
  int i, st = SU_NAME_STATE;

  switch (*ctype) {
  case OBJC_CTYPE_ID_CODE:
    if (!id_ctype) {
      entry = objc_scope_get_type(sc, OBJC_TYPE_ID_NAME);
      id_ctype = objc_scope_entry_get_ctype(entry);
    }
    ctype = id_ctype;
    break;
  case OBJC_CTYPE_CLASS_CODE:
    if (!cls_ctype) {
      entry = objc_scope_get_type(sc, OBJC_TYPE_CLASS_NAME);
      cls_ctype = objc_scope_entry_get_ctype(entry);
    }
    ctype = cls_ctype;
    break;
  case OBJC_CTYPE_SEL_CODE:
    if (!sel_ctype) {
      entry = objc_scope_get_type(sc, OBJC_TYPE_SEL_NAME);
      sel_ctype = objc_scope_entry_get_ctype(entry);
    }
    ctype = sel_ctype;
    break;
  }

  /* adhoc */
  st_ctype = strchr(ctype, OBJC_CTYPE_STRUCT_BEGIN_CODE);
  un_ctype = strchr(ctype, OBJC_CTYPE_UNION_BEGIN_CODE);
  if (!st_ctype)
    ctype = un_ctype;
  else if (!un_ctype)
    ctype = st_ctype;
  else if (st_ctype < un_ctype)
    ctype = st_ctype;
  else
    ctype = un_ctype;

  ctype++;
  temp = ALLOC_N(char, strlen(ctype)+1);
  i = 0;
  while (*ctype) {
    switch (st) {
    case SU_NAME_STATE:
      switch (*ctype) {
      case OBJC_CTYPE_SU_NAME_SEP_CODE:
        st = MEM_NAME_STATE;
        *temp = 0;
        i = 0;
        break;
      case OBJC_CTYPE_MEM_NAME_SEP_CODE:
        st = MEM_NAME_STATE;
        ctype++;
        if (STREQ(temp, name)) {
          scan_mem_ctype(&ctype, temp);
          return temp;
        } else {
          scan_mem_ctype(&ctype, NULL);
          *temp = 0;
          i = 0;
          break;
        }
      case OBJC_CTYPE_TERM_CODE:
        *temp = 0;
        i = 0;
        break;
      case OBJC_CTYPE_STRUCT_BEGIN_CODE:
      case OBJC_CTYPE_UNION_BEGIN_CODE:
        scan_mem_ctype(&ctype, NULL);
        break;
      case OBJC_CTYPE_STRUCT_END_CODE:
      case OBJC_CTYPE_UNION_END_CODE:
        /* self-reference or no members */
        {
          objc_scope_entry_t entry;

          entry = objc_scope_get_struct(sc, temp);
          if (entry) {
            return refer_struct(sc, objc_scope_entry_get_ctype(entry), name);
          } else
            goto not_found;
        }
      default:
        temp[i++] = *ctype;
        temp[i] = 0;
        break;
      }
      break;

    case MEM_NAME_STATE:
      switch (*ctype) {
      case OBJC_CTYPE_MEM_NAME_SEP_CODE:
        ctype++;
        if (STREQ(temp, name)) {
          scan_mem_ctype(&ctype, temp);
          return temp;
        } else {
          scan_mem_ctype(&ctype, NULL);
          *temp = 0;
          i = 0;
          break;
        }
      case OBJC_CTYPE_TERM_CODE:
        *temp = 0;
        i = 0;
        break;
      case OBJC_CTYPE_STRUCT_END_CODE:
      case OBJC_CTYPE_UNION_END_CODE:
        goto not_found;
      default:
        temp[i++] = *ctype;
        temp[i] = 0;
        break;
      }
      break;
    }
    ctype++;
  }

not_found:
  return NULL;
}

static void
scan_mem_ctype(const char **ctype, char *buf)
{
  int nest = 0;

  while (**ctype) {
    switch (**ctype) {
    case OBJC_CTYPE_TERM_CODE:
      if (nest == 0) {
        if (buf)
          *buf = 0;
        return;
      }
      break;
    case OBJC_CTYPE_STRUCT_BEGIN_CODE:
    case OBJC_CTYPE_UNION_BEGIN_CODE:
    case OBJC_CTYPE_ENUM_BEGIN_CODE:
    case OBJC_CTYPE_FUNC_BEGIN_CODE:
      nest++;
      break;
    case OBJC_CTYPE_STRUCT_END_CODE:
    case OBJC_CTYPE_UNION_END_CODE:
    case OBJC_CTYPE_ENUM_END_CODE:
    case OBJC_CTYPE_FUNC_END_CODE:
      nest--;
      break;
    }
    if (buf)
      *(buf++) = **ctype;
    (*ctype)++;
  }
}

static const char *
get_size_t_ctype(objc_scope_t sc)
{
  static const char *size_t_ctype = NULL;
  objc_scope_entry_t entry;

  if (!size_t_ctype) {
    entry = objc_scope_get_type(sc, "size_t");
    size_t_ctype = objc_scope_entry_get_ctype(entry);
  }
  return size_t_ctype;
}

static char *
refer_ptr(const char *ctype)
{
  char *refer;

  refer = ALLOC_N(char, strlen(ctype)+1);
  switch (*ctype) {
  case OBJC_CTYPE_PTR_CODE:
  case OBJC_CTYPE_ARRAY_CODE:
    sprintf(refer, "%s", ctype+1);
    return refer;

  case OBJC_CTYPE_FUNC_BEGIN_CODE:
    sprintf(refer, "%s", ctype);
    return refer;
  
  default:
    objc_print_error("refer_ptr(): cannot refer pointer -- %s", ctype);
    return NULL;
  }
}

static char *
refer_address(const char *ctype)
{
  char *refer;

  refer = ALLOC_N(char, strlen(ctype)+2);
  sprintf(refer, "%c%s", OBJC_CTYPE_PTR_CODE, ctype);
  return refer;
}

static void
encode_msg_expr(char **buf, objc_scope_t sc, objc_node_t recv,
                const char *sel, BOOL self_is_inst)
{
  char *recv_type, *ctype, *mtype;
  objc_ctype_info_t info;
  objc_method_sig_t sig;

  recv_type = objc_ctype_from_expr(sc, recv);
  info = objc_ctype_get_info(recv_type, objc_scope_get_class(sc), self_is_inst);
  sig = objc_class_get_method_sig(info->cls, sel, info->is_inst);
  if (!sig)
    sig = objc_get_global_method_sig(sel);
  if (!sig)
    objc_print_error("encode_msg_expr(): method signature '%s' not found ", sel);
  ctype = objc_method_sig_make_imp_ctype(sig, recv_type,
                                         info->cls, info->is_inst);
  mtype = apply_func(ctype);
  *buf += sprintf(*buf, "%s", mtype);
}
  
BOOL
objc_ctype_is_object(const char *ctype)
{
  switch (*ctype) {
  case OBJC_CTYPE_ID_CODE:      case OBJC_CTYPE_CLASS_CODE:
  case OBJC_CTYPE_SELF_CODE:    case OBJC_CTYPE_SUPER_CODE:
  case OBJC_CTYPE_XSELF_CODE:   case OBJC_CTYPE_INSTANCE_CODE:
  case OBJC_CTYPE_NIL_CODE:
    return YES;
  default:
    return NO;
  }
}

objc_ctype_info_t
objc_ctype_get_info(const char *ctype,
                    objc_class_t self_cls,
                    BOOL is_inst_meth)
{
  objc_ctype_info_t info;
  size_t name_len;
  char *comma, *prtcp, *prtc;

  info = ALLOC(struct objc_ctype_info);
  info->name = NULL;
  info->cls = NULL;
  info->prtcs = NULL;
  info->is_id = *ctype == OBJC_CTYPE_ID_CODE;
  info->is_declared = NO;
  info->is_super = NO;

  if (*ctype == OBJC_CTYPE_SELF_CODE) {
    info->is_inst = is_inst_meth;
    info->name = ALLOC_N(char, strlen(self_cls->name)+1);
    strcpy(info->name, self_cls->name);
    info->cls = self_cls;
  } else if (*ctype == OBJC_CTYPE_SUPER_CODE) {
    info->is_super = YES;
    info->cls = self_cls->superclass;
    info->is_inst = is_inst_meth;
  } else
    info->is_inst = *ctype != OBJC_CTYPE_CLASS_CODE;

  ctype++;
  if (*ctype == '{') {
    ctype++;

    /* class name */
    if (strchr(ctype, '<')) 
      name_len = strchr(ctype, '<') - ctype;
    else
      name_len = strchr(ctype, '}') - ctype;
    info->name = ALLOC_ATOM_N(char, name_len+1);
    strncpy(info->name, ctype, name_len);
    info->name[name_len] = 0;

    /* protocol names */
    prtcp = strchr(ctype, '<');
    if (prtcp) {
      prtcp++;
      for (;;) {
        comma = strchr(prtcp, ',');
        if (!comma)
          comma = strchr(prtcp, '>');
        prtc = ALLOC_ATOM_N(char, (size_t)(comma - prtcp + 1));
        strncpy(prtc, prtcp, comma - prtcp);
        prtc[comma - prtcp] = 0;
        objc_str_list_add_set(&info->prtcs, prtc);
        if (*comma == '>')
          break;
        else
          prtcp = comma + 1;
      }
    }

    if (!info->cls) {
      info->cls = objc_get_class(info->name);
      info->is_declared = st_lookup(objc_declared_class_table, info->name, NULL);
    }
  }

  return info;
}

